import DDRStub::*;
`timescale 1ns / 1ps

`define DEBUG

// INTERFACE with input clock
interface DDRInterface(input clk);

  chip ddr_chip[8];
  
  bit   [NUM_BANKS-1:0] open_bank = '0;
  bit                   write_data;
  bit                   read_write_bit;
  bit                   data_enable;
  bit                   enable_command;
  
  int                   tWR = 0; 
  int                   tRP = 0;
  int                   tRAS = 0;
  int                   tRRD = 0;
  int                   trefresh = 0;
  
  logic [1:0]           command;
  logic                 data_write;
  logic [ROW_LINES-1:0]           a_bus;
  
  
  logic [1:0]   command_bus;      
  logic [ROW_LINES-1:0]   row_address = 0;  
  logic [DATA_WIDTH-1:0]  d_bus;  
  logic [DATA_WIDTH-1:0]  dq;
  logic [31:0]  control_register;  
  logic         bank_bus;  
  logic         read_signal_bus;
  logic         write_signal_bus;
      
  bit           enable_dqs = 0;
  
  reg   [ROW_LINES-1:0]     col_address = 0;
  reg   [2:0]     bank; 
  reg             dqs;
  reg   [DATA_WIDTH-1:0]    data_buffer;
  

  int count = 0;
  int burst_length = 0; 
   
  time refresh_time;
  time precharge_time;

  clocking poscb @(posedge clk);
      default input #1ns output #3ns;
      inout     dq;
      input     dqs;
      output    write_data;      
  endclocking
  
  clocking negcb @(negedge clk);
    default input #1ns output #3ns;
    inout dq;
  endclocking
    
  assign a_bus            =     (read_write_bit)?row_address:col_address; 
  assign bank_bus         =     bank;
  assign command_bus      =     (enable_command)?command:1'bx;
  
  assign d_bus            =      dq;
    
  always @(clk)
  begin
    if(enable_dqs == 1)
       dqs = clk;          
    else
       dqs = 0;
  end
    
  always @(posedge clk)
  begin
    tWR++;
    tRP++;
    tRAS++;
    trefresh++;
     count++;    
   end
    
  always @(dqs)
    begin    
        data_buffer = { ddr_chip[7].bank_number[bank].mem[row_address][col_address],
                        ddr_chip[6].bank_number[bank].mem[row_address][col_address],
                        ddr_chip[5].bank_number[bank].mem[row_address][col_address],
                        ddr_chip[4].bank_number[bank].mem[row_address][col_address],
                        ddr_chip[3].bank_number[bank].mem[row_address][col_address],
                        ddr_chip[2].bank_number[bank].mem[row_address][col_address],
                        ddr_chip[1].bank_number[bank].mem[row_address][col_address],
                        ddr_chip[0].bank_number[bank].mem[row_address][col_address]
                      };
        
        dq = data_buffer;
        
        `ifdef DEBUG
        $display("At time %0t READ : Bank = %0h Row = %0h, Col = %0h, Data = %0h", 
                  $time, bank, row_address, col_address, dq);
        `endif
        
        col_address++;
        burst_length++;
        if(burst_length == 2)
          enable_dqs = 0;        
  end
  
  always @(clk)
  begin         
        if(write_data == 1)
          begin
            data_buffer = dq;
            ddr_chip[7].bank_number[bank].mem[row_address][col_address] = data_buffer[63:56];             
            ddr_chip[6].bank_number[bank].mem[row_address][col_address] = data_buffer[55:48];            
            ddr_chip[5].bank_number[bank].mem[row_address][col_address] = data_buffer[47:40];             
            ddr_chip[4].bank_number[bank].mem[row_address][col_address] = data_buffer[39:32];              
            ddr_chip[3].bank_number[bank].mem[row_address][col_address] = data_buffer[31:24];              
            ddr_chip[2].bank_number[bank].mem[row_address][col_address] = data_buffer[23:16];             
            ddr_chip[1].bank_number[bank].mem[row_address][col_address] = data_buffer[15:8];             
            ddr_chip[0].bank_number[bank].mem[row_address][col_address] = data_buffer[7:0];
            
            `ifdef DEBUG
            $display("At time %0t WRITE: Bank = %0h Row = %0h, Col = %0h, Data = %0h", 
                            $time, bank, row_address, col_address, data_buffer);
            `endif
            col_address++;
      end
    
  end
  
  
  task automatic read_data();
      count = 0;
      wait(count  > 8)
      @(posedge clk)      
      enable_dqs = 1; 
      burst_length = 0;         
  endtask  
  
  task automatic read(input logic[COL_LINES-1:0] column);
    
         Bank:assert (open_bank[bank] == 1)
              else $error("At time %0t ERROR: Cannot Read or Write - Bank %0b is not Activated.
                            Data displayed might be wrong", $time, bank);
                
         TRCD: assert(count > 3)
               else $error("Low TRCD delay. Data displayed might be wrong\n");
                 
         read_write_bit = 1;
                          
         @(posedge clk) 
         enable_command = 1;
         command = 2'b11;
         col_address = column;        
         
         @(negedge clk)
         enable_command = 0;
              
         read_data(); 
         tWR = 0;      
  endtask
  
  
   // WRITE TASK
  task automatic write(input logic[COL_LINES-1:0] column);    
      
      
      row: assert (open_bank[bank] == 1)
           else $error("At time %0t ERROR: Cannot Read or Write - Bank %0b is not Activated.
                            Data displayed might be wrong\n", $time, bank);
                
      TRCD: assert(count > 3)
               else $error("Low TRCD delay. Data displayed might be wrong");
                 
      read_write_bit = 1;
             
      @(posedge clk) 
      enable_command = 1;
      command = 2'b10;
      col_address = column;
             
      @(negedge clk)
      enable_command = 0;
      
      
      tWR = 0;
  endtask
    
  task automatic activate(input logic inbank, 
                          input logic[ROW_LINES-1:0] row);
        
        
        tRRD_violation: assert(tRRD == 0 || tRRD > 2)
        else $error("ACTIVATE TO ACTIVATE tRRD VOILATION");
        tRP_violation: assert(tRP == 0 || tRP > 1)
        else $error("PRECHARGE TO ACTIVATE tRP VIOLATION");
        
        read_write_bit = 0;
         
        
        @(posedge clk) 
        enable_command = 1;
        command = 2'b00;
        bank = inbank;
        row_address = row;
        
        @(negedge clk) 
        enable_command = 0;
       
        
        open_bank[inbank] = 1;
        
        `ifdef DEBUG
          $display("At time %0t ACT  : Bank = %0h Row = %0h\n",$time, bank, row_address);
        `endif
	      count = 0;
	      tRRD = 0;
	      tRAS = 0;         
    endtask 
    
  task automatic precharge(input logic bank);
     open_bank[bank] = 0;
     precharge_time = $time;
     
     tWR_time: assert(tWR == 0 || tWR > 3)
     else $error("READ/WRITE TO PRECHARGE tWR VIOLATION");
       
     tRAS_violation: assert(tRAS == 0 || tRAS > 5)
     else $error("ACTIVATE TO PRECHARGE tRAS VIOLATION");
     
     `ifdef DEBUG
        $display ("At time %0t PRE  : Bank = %0h\n", $time, bank);
     `endif    
       
     tRP = 0;
  endtask
    
  task automatic refresh;
   
    time1: assert(trefresh < TRC)
    else $error("Refresh violation at time %0t", $time);
    
    trefresh = 0;
    
  endtask
  
  task automatic nop (input logic[3:0] delay);
    logic [9:0] count;
      begin
        count = 10*delay;
        #count;
      end
  endtask
  
  sequence activate_to_activate;
    (command == 0) ##[0:$] ((command == 2) || (command == 3));
  endsequence
  
  property activate_simultaneous;
    @(posedge clk)
    (command == 0) |-> activate_to_activate;
  endproperty
  P1: assert property(activate_simultaneous);
  
endinterface


